﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Koopakiller.Numerics.FormulaSolver
{
    /// <summary>
    /// Represents a predictable formula.
    /// </summary>
    public sealed class Formula
    {
        /// <summary>
        /// Gets the symbol of the formula result.
        /// </summary>
        public string ResultSymbol { get; private set; }

        /// <summary>
        /// Gets a list of the symbols of required parameters.
        /// </summary>
        public List<string> RequiredParameterSymbols { get; private set; }

        /// <summary>
        /// Gets or sets a identifier for the formula.
        /// </summary>
        public FormulaIdentifier Identifier { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="Formula"/> class.
        /// </summary>
        /// <param name="resultSymbol">The symbol of the result.</param>
        /// <param name="requiredParameterSymbols">The symbols of the required parameters.</param>
        /// <param name="multiCalculator">A function to calculate multiply results from the parameters.</param>
        public Formula(string resultSymbol, string[] requiredParameterSymbols, Func<Dictionary<string, double?>, IEnumerable<Dictionary<string, double?>>> multiCalculator)
        {
            this.CanCalculate = null;
            this.ResultSymbol = resultSymbol;
            this.RequiredParameterSymbols = new List<string>();
            this.RequiredParameterSymbols.AddRange(requiredParameterSymbols);
            this.MultiCalculator = multiCalculator;
            this._IsSplittable = true;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="Formula"/> class.
        /// </summary>
        /// <param name="resultSymbol">The symbol of the result.</param>
        /// <param name="requiredParameterSymbols">The symbols of the required parameters.</param>
        /// <param name="multiCalculator">A function to calculate a unique result from the parameters.</param>
        public Formula(string resultSymbol, string[] requiredParameterSymbols, Func<Dictionary<string, double?>, double> multiCalculator)
        {
            this.CanCalculate = null;
            this.ResultSymbol = resultSymbol;
            this.RequiredParameterSymbols = new List<string>();
            this.RequiredParameterSymbols.AddRange(requiredParameterSymbols);
            this.Calculator = multiCalculator;
        }

        private bool _IsSplittable = false;
        /// <summary>
        /// Gets a value that determines the formula calculate a unique result.
        /// </summary>
        private bool IsSplittable
        {
            get
            {
                return this._IsSplittable;
            }
        }

        /// <summary>
        /// Calculate the formula with the given values.
        /// </summary>
        /// <param name="values">The given values for the formula.</param>
        /// <returns>The result of the formula. If <see cref="IsSplittable"/> is <c>true</c>, it can contains a list of multiply results.</returns>
        public IEnumerable<Dictionary<string, double?>> Calculate(Dictionary<string, double?> values)
        {
            if (this._IsSplittable)
                foreach (var x in MultiCalculator(values))
                    yield return x;
            else
            {
                values[this.ResultSymbol] = Calculator(values);
                yield return values;
            }
        }

        private Func<Dictionary<string, double?>, double> Calculator;
        private Func<Dictionary<string, double?>, IEnumerable<Dictionary<string, double?>>> MultiCalculator;

        /// <summary>
        /// Gets or sets a function to check if the Formula can be used.
        /// </summary>
        public Func<Dictionary<string, double?>, bool> CanCalculate { get; set; }
    }
}
